#!/usr/bin/python

# btpeer.py

import socket
import struct
import threading
import time
import traceback
import cPickle
import sys
from multiprocessing import Process, Lock
from xml.dom.minidom import Document
from xml.dom import minidom, Node
from encryption_util import *
from M2Crypto import *
import threading
import socket

DATA = "DATA"

####################################################################################
########################### UTILITIES ##############################################
####################################################################################

def btdebug( msg ):
    """ Prints a messsage to the screen with the name of the current thread """
    print "[%s] %s" % ( str(threading.currentThread().getName()), msg )

#--------------------------------------------------------------------------
def typeOfMessage( xmlData ):
#--------------------------------------------------------------------------
    if xmlData == None :
        print "Kapoios stokos pou esteile None"
        return "DEFAULT" , "None:None"
    try:
        doc = minidom.parseString(xmlData)
        msgType = str( doc.firstChild.nodeName )

        return msgType , xmlData

    except:
        print ("Debug message : " , xmlData)
        traceback.print_exc()   
        return "DEFAULT" , "None:None"


#--------------------------------------------------------------------------
def sourceAndDestination ( xmlData ) :
#--------------------------------------------------------------------------
    try:

        doc = minidom.parseString(xmlData)

        data = doc.documentElement

        master = (data.getElementsByTagName("MasterHost"))[0]
        masterIp = str( master.getAttribute("Ip") )
        masterPort = str( master.getAttribute("Port") )

        slave = (data.getElementsByTagName("SlaveHost"))[0]
        slaveIp = str( slave.getAttribute("Ip") )
        slavePort = str( slave.getAttribute("Port") )

        return ( masterIp , int(masterPort) ) , ( slaveIp , int(slavePort) )

    except:

        traceback.print_exc()   
        return "DEFAULT" , "None:None"


#--------------------------------------------------------------------------
def extractData ( msgdata ) :
#--------------------------------------------------------------------------
        try :
            doc = minidom.parseString( msgdata )
            data = doc.documentElement
            data = (data.getElementsByTagName("Packet"))[0]

            typeMsg = str( data.getAttribute("Type") )
            pkt = str( data.getAttribute("Value") )

            return ( typeMsg , pkt )

        except :
            traceback.print_exc()
            print "Failed extracting data"
            return ( None , None )


#--------------------------------------------------------------------------
def createXMLcontent( masterIp , masterPort , slaveIp, slavePort,  msgType , content , xmlType=DATA):
#--------------------------------------------------------------------------

    # Creating XML to send
    doc = Document()
    dataElem = doc.createElement( xmlType )
    doc.appendChild( dataElem )

    masterElem = doc.createElement( "MasterHost" )
    masterElem.setAttribute( "Ip" , masterIp)
    masterElem.setAttribute( "Port" , str(masterPort) )
    dataElem.appendChild( masterElem )

    slaveElem = doc.createElement( "SlaveHost" )
    slaveElem.setAttribute( "Ip" , slaveIp)
    slaveElem.setAttribute( "Port" , str(slavePort))
    dataElem.appendChild( slaveElem )

    msgElem = doc.createElement( "Packet" )
    msgElem.setAttribute( "Value" , content)
    msgElem.setAttribute( "Type" , msgType )
    dataElem.appendChild( msgElem )

    sendingDoc = doc.toprettyxml()

#    print ( "Sending" , sendingDoc )
    return sendingDoc


#==============================================================================
class Peer:
    """ Implements the core functionality that might be used by a peer in a
    P2P network.

    """

    #--------------------------------------------------------------------------
    def __init__( self, localPort, myid=None, serverhost = None ):
    #--------------------------------------------------------------------------
        """ Initializes a peer servent (sic.) with the ability to catalog
        information for up to maxpeers number of peers (maxpeers may
        be set to 0 to allow unlimited number of peers), listening on
        a given server port , with a given canonical peer name (id)
        and host address. If not supplied, the host address
        (serverhost) will be determined by attempting to connect to an
        Internet host like Google.     """
        self.debug = 1
    
        self.serverport = int(localPort)
        if serverhost: self.serverhost = serverhost
        else: self.__initserverhost()
        if myid: self.myid = myid
        else: self.myid = (self.serverhost, self.serverport)
    
        self.peerlock = threading.Lock()  # ensure proper access to
                                    # peers list (maybe better to use
                                    # threading.RLock (reentrant))
        
        self.shutdown = False  # used to stop the main loop
    
        self.handlers = {}
        self.router = None



    #--------------------------------------------------------------------------
    def __initserverhost( self ):
    #--------------------------------------------------------------------------
        """ Attempt to connect to an Internet host in order to determine the
        local machine's IP address.
    
        """
        s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        s.connect( ( "www.google.com", 80 ) )
        self.serverhost = s.getsockname()[0]
        s.close()



    #--------------------------------------------------------------------------
    def __debug( self, msg ):
    #--------------------------------------------------------------------------
        if self.debug:
            btdebug( msg )


    #--------------------------------------------------------------------------
    def __handlepeer( self, clientsock ):
    #--------------------------------------------------------------------------
        """
        handlepeer( new socket connection ) -> ()
    
        Dispatches messages from the socket connection
        """
    
        self.__debug( 'New child ' + str(threading.currentThread().getName()) )
        self.__debug( 'Connected ' + str(clientsock.getpeername()) )
    
        host, port = clientsock.getpeername()
        peerconn = BTPeerConnection( None, host, port, clientsock, debug=True )


        try:

            xmlData = peerconn.recvdata()
            msgtype, msgdata = typeOfMessage( xmlData )

            if msgtype: msgtype = msgtype.upper()
            if msgtype not in self.handlers:
                self.__debug( 'Not handled: %s' % msgtype, )
            else:
                self.__debug( 'Handling peer msg: %s' % msgtype )
            self.handlers[ msgtype ]( peerconn, msgdata )

        except KeyboardInterrupt:
            peerconn.close()
            raise
        except:
            print ("Debug Message from Handler : ," , xmlData)
            if self.debug:
                traceback.print_exc()
        
        self.__debug( 'Disconnecting ' + str(clientsock.getpeername()) )
        peerconn.close()
    
        # end handlepeer method

    

    #--------------------------------------------------------------------------
    def addhandler( self, msgtype, handler ):
    #--------------------------------------------------------------------------
        """ Registers the handler for the given message type with this peer """
        #assert len(msgtype) == 4
        self.handlers[ msgtype ] = handler


    #--------------------------------------------------------------------------
    def makesecureserversocket( self, port, file, backlog=5 ):
    #--------------------------------------------------------------------------
        """ Constructs and prepares a secure server socket listening on the given 
        port.
    
        """
        ctx = SSL.Context('sslv3')
        certfile= file+".pem"
        keyfile=file+".key"
        ctx.load_cert( certfile, keyfile, call )
        bindsocket = SSL.Connection(ctx)
        bindsocket.bind(('', port))
        bindsocket.listen(backlog)
        #s, fromaddr = bindsocket.accept()

        return bindsocket


    #--------------------------------------------------------------------------
    def sendtopeer( self, peerid,  msgdata, waitreply=True ):
    #--------------------------------------------------------------------------
        """
        sendtopeer( peer id, message type, message data, wait for a reply )
         -> [ ( reply type, reply data ), ... ] 
    
        Send a message to the identified peer. In order to decide how to
        send the message, the router handler for this peer will be called.
        If no router function has been registered, it will not work. The
        router function should provide the next immediate peer to whom the 
        message should be forwarded. The peer's reply, if it is expected, 
        will be returned.
    
        Returns None if the message could not be routed.
        """
    
        if self.router:
            nextpid, host, port = self.router( peerid )
        if not self.router or not nextpid:
            self.__debug( 'Unable to route %s to %s' % (msgtype, peerid) )
            return None
        #host,port = self.peers[nextpid]
        return self.connectandsend( host, port, msgdata,
                        pid=nextpid,
                        waitreply=waitreply )
    


    #--------------------------------------------------------------------------
    def connectandsend( self, host, port, msgdata, 
            pid=None, waitreply=True ):
    #--------------------------------------------------------------------------
        """
        connectandsend( host, port,  message data, peer id,
        wait for a reply ) -> [ ( reply type, reply data ), ... ]
    
        Connects and sends a message to the specified host:port. The host's
        reply, if expected, will be returned as a list of tuples.
    
        """
        msgreply = []
        try:
            peerconn = BTPeerConnection( pid, host, port, debug=self.debug )
            peerconn.senddata( msgdata )
            self.__debug( 'Sent %s' % (pid) )


	    onereply = (None)            
            if waitreply:
		print("Waiting for reply")
		onereply = peerconn.recvdata()

	    while ( onereply != (None) ):
		print("Reply : ", onereply)
                msgreply.append( onereply )
                self.__debug( 'Answering To Request %s: %s' % ( pid, str(msgreply) ) )
                onereply = peerconn.recvdata()

            peerconn.close()
        except KeyboardInterrupt:
            raise
        except:
            if self.debug:
                traceback.print_exc()
    
        return msgreply
    
        # end connectsend method


    #--------------------------------------------------------------------------
    def checklivepeers( self ):
    #--------------------------------------------------------------------------
        """ Attempts to ping all currently known peers in order to ensure that
        they are still active. Removes any from the peer list that do
        not reply. This function can be used as a simple stabilizer.
    
        """
        todelete = []
        for pid in self.peers:
            isconnected = False
            try:
                self.__debug( 'Check live %s' % pid )
                host,port = self.peers[pid]
                peerconn = BTPeerConnection( pid, host, port, debug=self.debug )
                peerconn.senddata( 'PING', '' )
                isconnected = True
            except:
                todelete.append( pid )
            if isconnected:
                peerconn.close()
    
        self.peerlock.acquire()
        try:
            for pid in todelete: 
                if pid in self.peers: del self.peers[pid]
        finally:
            self.peerlock.release()
        # end checklivepeers method

    
    #--------------------------------------------------------------------------
    def mainloop( self ):
    #--------------------------------------------------------------------------
        s = self.makesecureserversocket( self.serverport, "client_certificate/client" )

        self.__debug( 'Server started: %s (%s:%d)' % ( self.myid, self.serverhost, self.serverport ) )
        
        while not self.shutdown:
            try:
                self.__debug( 'Listening for connections...' )
                clientsock, clientaddr = s.accept()
        
                #self.__handlepeer(clientsock)
                t = threading.Thread( target = self.__handlepeer,
                              args = [ clientsock ] )
                t.start()

	    except KeyboardInterrupt:
		    print 'KeyboardInterrupt: stopping mainloop'
		    sys.exit(0)
	    except:
		    traceback.print_exc()
		    sys.exit(0)
    
        # end while loop
        self.__debug( 'Main loop exiting' )
    
        s.close()
    

# end BTPeer class




# **********************************************************




class BTPeerConnection:

    #--------------------------------------------------------------------------
    def __init__( self, peerid, host, port, sock=None, debug=False, file=None ):
    #--------------------------------------------------------------------------
    # any exceptions thrown upwards
    
        self.id = peerid
        self.debug = debug
    
        if not sock:
            ctx = SSL.Context('sslv3')
            if (file != None):
                certfile= file+".pem"
                keyfile=file+".key"
                ctx.load_cert( certfile, keyfile, call )
            self.s = SSL.Connection(ctx)

		    #self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
            self.s.connect(( host , int ( port ) ))

        else:
            self.s = sock
    
        self.sd = self.s.makefile( 'rw', 0 )
    

    #--------------------------------------------------------------------------
    def clientInfo( self ):
    #--------------------------------------------------------------------------    
        return (self.s.getpeername())


#--------------------------------------------------------------------------
    def myInfo( self ):
    #--------------------------------------------------------------------------    
        return (self.s.getsockname())


    #--------------------------------------------------------------------------
    def clientSocket( self ):
    #--------------------------------------------------------------------------    
        return (self.s)
   


    #--------------------------------------------------------------------------
    def __makemsg( self, msgdata , msgtype):
    #--------------------------------------------------------------------------
        msgdata = str( msgdata )
        #xmlData = createXmlString(msgtype, msgdata)

        msglen = len( msgdata )
        msg = struct.pack( "!4sL%ds" % msglen, msgtype, msglen, msgdata )
        return msg


    #--------------------------------------------------------------------------
    def __debug( self, msg ):
    #--------------------------------------------------------------------------
        if self.debug:
            btdebug( msg )


    #--------------------------------------------------------------------------
    def senddata( self, msgdata , msgtype=DATA):
    #--------------------------------------------------------------------------
        """
        senddata( message type, message data ) -> boolean status
    
        Send a message through a peer connection. Returns True on success
        or False if there was an error.
        """
    
        try:
            msg = self.__makemsg( msgdata, msgtype )
            self.sd.write( msg )
            self.sd.flush()
        except KeyboardInterrupt:
            raise
        except:
            if self.debug:
                traceback.print_exc()
            return False
        return True
        

    #--------------------------------------------------------------------------
    def recvdata( self ):
    #--------------------------------------------------------------------------
        """
        recvdata() -> (msgtype, msgdata)
    
        Receive a message from a peer connection. Returns (None, None)
        if there was any error.
        """
    
        try:
            msgtype = self.sd.read( 4 )
            #if not msgtype: return (None, None)
                
            lenstr = self.sd.read( 4 )    
            #print ( "Received LEN" , lenstr )     
            msglen = int(struct.unpack( "!L", lenstr )[0])
            msg = ""
    
            while len(msg) != msglen:
                data = self.sd.read( min(2048, msglen - len(msg)) )
                if not len(data):
                    break
                msg += data
    
            if len(msg) != msglen:
                return (None)
    
        except KeyboardInterrupt:
            raise
        except:
            if self.debug:
                traceback.print_exc()
            return (None)
    
        return ( msg )
    
        # end recvdata method


    #--------------------------------------------------------------------------
    def close( self ):
    #--------------------------------------------------------------------------
        """
        close()
    
        Close the peer connection. The send and recv methods will not work
        after this call.
        """
    
        self.s.close()
        self.s = None
        self.sd = None


    #--------------------------------------------------------------------------
    def __str__( self ):
    #--------------------------------------------------------------------------
        return "|%s|" % peerid



